/*
 * Copyright (C) 2002, Simon Nieuviarts
 *               2010, Gregory Mounie
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <time.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include "readcmd.h"
#include "ensishell.h"

#define VARIANTE "7"

typedef struct t_ps ps;

ps* ps_list = NULL;
struct timeval tv_start, tv_end, lim_start, lim_end;
int bg_flag;
int soft_lim = -1, hard_lim = -1;
int lim_flag = 0;
int pipe_flag = 0;
int child_over;

int main()
{
	printf("%s\n", VARIANTE);
	while (1) 
	{
		struct cmdline *l;
		char *prompt = "> ";
		int i,j;
		l = readcmd(prompt);

		bg_flag = l->bg;
		if (!l) 
		{
			printf("exit\n");
			exit(0);
		}

		if (l->err)
		{
			printf("error: %s\n", l->err);
			continue;
		}
		
		int pid, pid2, input, output, status;
		pipe_flag = 0;
		struct sigaction new_action, lim_handler;
		new_action.sa_handler = child_died;
	    sigemptyset(&new_action.sa_mask);
		new_action.sa_flags = 0;
		lim_handler.sa_handler = limit_handler;
	    sigemptyset(&lim_handler.sa_mask);
		lim_handler.sa_flags = 0;
		int counter = 0;
		 
		if(l->in)
		{
			input = open(l->in, O_RDONLY);
		}
		if(l->out)
		{
			output = open(l->out, O_WRONLY | O_CREAT, 00700);
		}
		pipe_flag = 0;
		if(l->seq[1] != 0)
		{
			pipe_flag = 1;
		}
		
		char **cmd = l->seq[0];
		
		if(!strcmp(cmd[0], "ulimit"))
		{
			if(cmd[1] != NULL){
				printf("%s\n", cmd[1]);
				if(atoi(cmd[1]) == -1) // TODO Why this?
				{
					soft_lim = -1;
					hard_lim = -1;
					lim_flag = 0;
				}
				else
				{
					soft_lim = atoi(cmd[1]);
					hard_lim = soft_lim + 5;
					lim_flag = 1;
				}
			}
			else{
				printf("usage: ulimit t, where t is the time to wait before the soft limit of the execution time");
			}
		}
		else if(!strcmp(cmd[0], "liste_ps"))
		{
			liste_ps();
		}
		
		else
		{
			pid = fork();
			switch(pid)
			{
			
				case -1:
					perror("");
					return -1;
					
				case 0: 
					if(pipe_flag)
					{
						char** cmd2 = l->seq[1];
						int a_pipe[2];
						pipe(a_pipe);
						pid2 = fork();
						if(pid2 == -1)
						{
							perror("");
							return -1;
						}
						if(pid2 == 0)
						{
							if(l->out)
							{
								dup2(output, 1);
								close(output);
							}
							dup2(a_pipe[0], 0);
							close(a_pipe[0]);
							close(a_pipe[1]);
							execvp(cmd2[0], cmd2);
							printf("Command not found: '%s'\n", cmd2[0]);
						}
					dup2(a_pipe[1], 1);
					close(a_pipe[0]);
					close(a_pipe[1]);
					}
					if(l->in)
					{
						dup2(input, 0);
						close(input);
					}
					if(!pipe_flag)
					{
						if(l->out)
						{
							dup2(output, 1);
							close(output);
						}
					}	 
					execvp(cmd[0], cmd); 
					printf("Command not found: '%s'\n", cmd[0]);
				
				default:
					if(l->bg)
					{
						gettimeofday(&tv_start, NULL);
						sigaction(SIGCHLD, &new_action, NULL);
						add_proc(pid, cmd);
					}
					else 
					{
						double elapsed = 0;
						gettimeofday(&lim_start, NULL);
						child_over = 0;
						sigaction(SIGCHLD, &lim_handler, NULL);
						if(lim_flag)
						{
							do
							{
								gettimeofday(&lim_end, NULL);
								elapsed = 1000000.0 * (lim_end.tv_sec -lim_start.tv_sec);
								elapsed += (lim_end.tv_usec - lim_start.tv_usec);
								elapsed = elapsed/1000000.0;
								if(elapsed >= (soft_lim+counter))
									{
									counter++;
									xcpu_handler(pid, counter);
									}
							}
							while(!child_over);
							
						}
						else
						{
							waitpid(pid, 0, 0);
						}
					}
			}
		}
	}
	return 0;
}

void add_proc(int pid, char** cmd)
{
	char new_cmd[STRING_SIZE] = "";
	if(ps_list == NULL)
	{
		ps_list = (ps*)malloc(sizeof(ps));
		ps_list->pid = pid;
		strcpy(ps_list->cmd, cmd_string(new_cmd, cmd));
		ps_list->next = NULL;
	}	
	else
	{
		ps* new_proc = (ps*)malloc(sizeof(ps));
		new_proc->pid = pid;	
		strcpy(new_proc->cmd, cmd_string(new_cmd, cmd));
		new_proc->next = ps_list;
		ps_list = new_proc;
	}
	
}

char* cmd_string(char* new_cmd, char** cmd)
{
	int i = 0;
	for(i = 0; cmd[i]!=0; i++)
	{
		strcat(new_cmd, cmd[i]);
		strcat(new_cmd, " ");  
	}
	return new_cmd;

}

void liste_ps()
{
	ps* aux_list = ps_list;
	int term;
	while(aux_list != NULL)
	{
	term = waitpid(aux_list->pid, 0, WNOHANG);
		if(!term)
		{
			printf("PID: [%d], Command: %s\n", aux_list->pid, aux_list->cmd);
		}
	aux_list = aux_list->next;
	}	
}

void child_died()
{
	if(bg_flag)
	{
	 	gettimeofday(&tv_end, NULL);
	 	double elapsed = 0;
		elapsed = 1000000.0 * (tv_end.tv_sec -tv_start.tv_sec);
	    elapsed += (tv_end.tv_usec - tv_start.tv_usec);
		printf("\nThe process has taken approximately %lf second(s) to execute.\n", elapsed/1000000.0);
		printf("> ");
	}
}

void xcpu_handler(int pid, int counter)
{
	if(counter == 5)
	{
		kill(pid, SIGKILL);
	}
	else
	{
		kill(pid, SIGXCPU);
	}
}

void limit_handler()
{
	child_over = 1;
}
